/*
 * Created by Gibbs on 2021/1/1.
 * Copyright (c) 2021 Gibbs. All rights reserved.
 */

#include <cstring>
#include <base/Log.h>
#include <j4a/CommonUtilJni.h>
#include "MediaCodecAudioDecoder.h"

extern "C" {
#include <demuxing/avformat_def.h>
}

#define TAG "MediaCodecAudioDecoder"

MediaCodecAudioDecoder::MediaCodecAudioDecoder() = default;

MediaCodecAudioDecoder::~MediaCodecAudioDecoder() = default;

static int sample2MediaCodecIndex(int sampleRate) {
    switch (sampleRate) {
        case 96000:
            return 0x00;
        case 88200:
            return 0x01;
        case 64000:
            return 0x02;
        case 48000:
            return 0x03;
        case 44100:
            return 0x04;
        case 32000:
            return 0x05;
        case 24000:
            return 0x06;
        case 22050:
            return 0x07;
        case 16000:
            return 0x08;
        case 12000:
            return 0x09;
        case 11025:
            return 0x0A;
        case 8000 :
            return 0x0B;
        default:
            return 0xFF;
    }
}

static uint8_t *formatAacCsd0(int sampleRate, int channelCount, int profile) {
    sampleRate = sample2MediaCodecIndex(sampleRate);
    uint16_t csd_0 = 0;
    csd_0 |= (profile << 11);
    csd_0 |= (sampleRate << 7);
    csd_0 |= (channelCount << 3);

    auto csdByteBuffer = static_cast<uint8_t *>(malloc(2));
    csdByteBuffer[0] = (csd_0 >> 8 & 0xFF);
    csdByteBuffer[1] = (csd_0 & 0xFF);

    return csdByteBuffer;
}

static int formatAacProfile(int ffmpegProfile) {
    switch (ffmpegProfile) {
        case FF_PROFILE_AAC_MAIN:
            return AACObjectMain;
        case FF_PROFILE_AAC_LOW:
            return AACObjectLC;
        case FF_PROFILE_AAC_SSR:
            return AACObjectSSR;
        case FF_PROFILE_AAC_LTP:
            return AACObjectLTP;
        case FF_PROFILE_AAC_HE:
            return AACObjectHE;
        case FF_PROFILE_AAC_HE_V2:
            return AACObjectHE_PS;
        case FF_PROFILE_AAC_LD:
            return AACObjectLD;
        case FF_PROFILE_AAC_ELD:
            return AACObjectELD;
        default:
            return AACObjectLC;
    }
}

void MediaCodecAudioDecoder::init(AVCodecParameters *codecParameters) {
    const char *mine = get_mime_by_codec_id(static_cast<CODEC_TYPE>(codecParameters->codec_id));
    std::string nameStr = CommonUtilJni::findDecoderForFormat(mine);
    const char *name = nameStr.c_str();
    mAMediaCodec = AMediaCodec_createCodecByName(name);
    if (!mAMediaCodec) {
        LOGE(TAG, "can not find name %s", name);
        mAMediaCodec = AMediaCodec_createDecoderByType(mine);
    }
    if (!mAMediaCodec) {
        LOGE(TAG, "can not find mine %s", mine);
        return;
    }

    int sampleRate = codecParameters->sample_rate;
    int channelCount = codecParameters->channels;
    int profile = formatAacProfile(codecParameters->profile);
    LOGI(TAG, "CoreFlow : init mine=%s,sampleRate=%d,channelCount=%d,profile=%d",
            mine, sampleRate, channelCount, profile);

    AMediaFormat *audioFormat = AMediaFormat_new();
    AMediaFormat_setString(audioFormat, AMEDIAFORMAT_KEY_MIME, mine);
    AMediaFormat_setInt32(audioFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, sampleRate);
    AMediaFormat_setInt32(audioFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount);
    AMediaFormat_setInt32(audioFormat, AMEDIAFORMAT_KEY_AAC_PROFILE, profile);
    AMediaFormat_setInt32(audioFormat, AMEDIAFORMAT_KEY_IS_ADTS, 1);
    if (codecParameters->extradata_size != 0) {
        AMediaFormat_setBuffer(audioFormat, "csd-0",
                               codecParameters->extradata, codecParameters->extradata_size);
    } else {
        AMediaFormat_setBuffer(audioFormat, "csd-0",
                               formatAacCsd0(sampleRate, channelCount, profile), 2);
    }

    media_status_t status = AMediaCodec_configure(mAMediaCodec, audioFormat, nullptr, nullptr, 0);
    if (status != AMEDIA_OK) {
        LOGE(TAG, "configure fail %d", status);
        AMediaCodec_delete(mAMediaCodec);
        mAMediaCodec = nullptr;
        return;
    }

    status = AMediaCodec_start(mAMediaCodec);
    if (status != AMEDIA_OK) {
        LOGE(TAG, "start fail %d", status);
        AMediaCodec_delete(mAMediaCodec);
        mAMediaCodec = nullptr;
        return;
    }
    auto outputFormat = AMediaCodec_getOutputFormat(mAMediaCodec);
    int32_t pcmEncoding;
    AMediaFormat_getInt32(outputFormat, "pcm-encoding", &pcmEncoding);
    LOGI(TAG, "format changed %s", AMediaFormat_toString(outputFormat));
    codecParameters->format = pcmEncoding;
    LOGI(TAG, "start successfully");
}

int MediaCodecAudioDecoder::send_packet(AVPacket *inPacket) {
    if (!mAMediaCodec) {
        return INVALID_CODEC;
    }

    ssize_t bufferId = AMediaCodec_dequeueInputBuffer(mAMediaCodec, 2000);
    if (bufferId >= 0) {
        uint32_t flag = 0;
        if ((inPacket->flags & AV_PKT_FLAG_KEY) == AV_PKT_FLAG_KEY) {
            flag = AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME;
        }
        // 获取buffer的索引
        size_t outsize;
        uint8_t *inputBuf = AMediaCodec_getInputBuffer(mAMediaCodec, bufferId, &outsize);
        if (inputBuf != nullptr && inPacket->size <= outsize) {
            // 将待解码的数据copy到硬件中
            memcpy(inputBuf, inPacket->data, inPacket->size);
            media_status_t status = AMediaCodec_queueInputBuffer(mAMediaCodec, bufferId, 0,
                                                                 inPacket->size, inPacket->pts,
                                                                 flag);
            return (status == AMEDIA_OK ? CODEC_SUCCESS : CODEC_ERROR);
        }
    }
    return TRY_AGAIN;
}

int MediaCodecAudioDecoder::receive_frame(MediaData *outFrame) {
    if (!mAMediaCodec) {
        return INVALID_CODEC;
    }

    AMediaCodecBufferInfo info;
    ssize_t bufferId = AMediaCodec_dequeueOutputBuffer(mAMediaCodec, &info, 2000);
    if (bufferId >= 0) {
        size_t outsize;
        uint8_t *outputBuf = AMediaCodec_getOutputBuffer(mAMediaCodec, bufferId, &outsize);
        if (outputBuf != nullptr) {
            extractFrame(outputBuf, outFrame, info);
            AMediaCodec_releaseOutputBuffer(mAMediaCodec, bufferId, false);
            return CODEC_SUCCESS;
        }
    } else if (bufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
        auto format = AMediaCodec_getOutputFormat(mAMediaCodec);
        int32_t pcmEncoding;
        AMediaFormat_getInt32(format, "pcm-encoding", &pcmEncoding);
        LOGI(TAG, "format changed %s", AMediaFormat_toString(format));
        return FORMAT_CHANGED;
    } else if (bufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {

    }
    return CODEC_ERROR;
}

void MediaCodecAudioDecoder::release() {
    if (mAMediaCodec) {
        AMediaCodec_flush(mAMediaCodec);
        AMediaCodec_stop(mAMediaCodec);
        AMediaCodec_delete(mAMediaCodec);
        mAMediaCodec = nullptr;
    }
}

void MediaCodecAudioDecoder::reset() {
    AMediaCodec_flush(mAMediaCodec);
}

void MediaCodecAudioDecoder::extractFrame(uint8_t *outputBuf, MediaData *outFrame,
                                          AMediaCodecBufferInfo info) {
    outFrame->pts = info.presentationTimeUs;
    outFrame->dts = outFrame->pts;
    uint32_t frameSize = info.size;
    memcpy(outFrame->data, outputBuf, frameSize);
    outFrame->size = frameSize;
}
